/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skgpu_ganesh_AtlasTextOp_DEFINED
#define skgpu_ganesh_AtlasTextOp_DEFINED

#include "include/core/SkColor.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkString.h"
#include "include/private/SkColorData.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkPoint_impl.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/gpu/AtlasTypes.h"
#include "src/gpu/ganesh/GrAppliedClip.h"
#include "src/gpu/ganesh/GrBuffer.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrColorInfo.h"
#include "src/gpu/ganesh/GrColorSpaceXform.h"
#include "src/gpu/ganesh/GrProcessorSet.h"
#include "src/gpu/ganesh/effects/GrDistanceFieldGeoProc.h"
#include "src/gpu/ganesh/ops/GrMeshDrawOp.h"
#include "src/gpu/ganesh/ops/GrOp.h"

#include <cstddef>
#include <cstdint>
#include <utility>

class GrDstProxyView;
class GrGeometryProcessor;
class GrMeshDrawTarget;
class GrOpFlushState;
class GrPaint;
class GrProgramInfo;
class GrRecordingContext;
class GrSurfaceProxy;
class GrSurfaceProxyView;
class SkArenaAlloc;
class SkFont;
class SkPaint;
enum class GrXferBarrierFlags;
struct GrShaderCaps;

namespace sktext {
namespace gpu {
class AtlasSubRun;
}
}

namespace skgpu::ganesh {
class SurfaceDrawContext;

class AtlasTextOp final : public GrMeshDrawOp {
public:
    DEFINE_OP_CLASS_ID

    ~AtlasTextOp() override
    {
        for (const Geometry *g = fHead; g != nullptr;) {
            const Geometry *next = g->fNext;
            g->~Geometry();
            g = next;
        }
    }

    void *operator new(size_t s);
    void operator delete(void *b) noexcept;
    static void ClearCache();

    struct Geometry {
        Geometry(const sktext::gpu::AtlasSubRun &subRun, const SkMatrix &drawMatrix, SkPoint drawOrigin,
            SkIRect clipRect, sk_sp<SkRefCnt> &&supportData, const SkPMColor4f &color)
            : fSubRun{ subRun },
              fSupportDataKeepAlive{ std::move(supportData) },
              fDrawMatrix{ drawMatrix },
              fDrawOrigin{ drawOrigin },
              fClipRect{ clipRect },
              fColor{ color }
        {
            SkASSERT(fSupportDataKeepAlive != nullptr);
        }

        static Geometry *Make(const sktext::gpu::AtlasSubRun &subRun, const SkMatrix &drawMatrix, SkPoint drawOrigin,
            SkIRect clipRect, sk_sp<SkRefCnt> &&supportData, const SkPMColor4f &color, SkArenaAlloc *alloc);

        void fillVertexData(void *dst, int offset, int count) const;

        const sktext::gpu::AtlasSubRun &fSubRun;

        // Keep the TextBlob or Slug alive until the op is deleted.
        sk_sp<SkRefCnt> fSupportDataKeepAlive;

        const SkMatrix fDrawMatrix;
        const SkPoint fDrawOrigin;

        // fClipRect is only used in the DirectMaskSubRun case to do geometric clipping.
        // TransformedMaskSubRun, and SDFTSubRun don't use this field, and expect an empty rect.
        const SkIRect fClipRect;

        // Color is updated after processor analysis if it was determined the shader resolves to
        // a constant color that we then evaluate on the CPU.
        // TODO: This can be made const once processor analysis is separated from op creation.
        SkPMColor4f fColor;
        Geometry *fNext{ nullptr };
    };

    const char *name() const override
    {
        return "AtlasTextOp";
    }

    void visitProxies(const GrVisitProxyFunc &) const override;

    FixedFunctionFlags fixedFunctionFlags() const override;

    GrProcessorSet::Analysis finalize(const GrCaps &, const GrAppliedClip *, GrClampType) override;

    enum class MaskType : uint32_t {
        kGrayscaleCoverage,
        kLCDCoverage,
        kColorBitmap,
#if !defined(SK_DISABLE_SDF_TEXT)
        kAliasedDistanceField,
        kGrayscaleDistanceField,
        kLCDDistanceField,
        kLCDBGRDistanceField,

        kLast = kLCDBGRDistanceField
#else
        kLast = kColorBitmap
#endif
    };
    inline static constexpr int kMaskTypeCount = static_cast<int>(MaskType::kLast) + 1;

#if defined(GR_TEST_UTILS)
    static GrOp::Owner CreateOpTestingOnly(skgpu::ganesh::SurfaceDrawContext *, const SkPaint &, const SkFont &,
        const SkMatrix &, const char *text, int x, int y);
#endif

private:
    friend class GrOp; // for ctor

    struct FlushInfo {
        sk_sp<const GrBuffer> fVertexBuffer;
        sk_sp<const GrBuffer> fIndexBuffer;
        GrGeometryProcessor *fGeometryProcessor;
        const GrSurfaceProxy **fPrimProcProxies;
        int fGlyphsToFlush = 0;
        int fVertexOffset = 0;
        int fNumDraws = 0;
    };

    AtlasTextOp(MaskType maskType, bool needsTransform, int glyphCount, SkRect deviceRect, Geometry *geo,
        const GrColorInfo &dstColorInfo, GrPaint &&paint);

    AtlasTextOp(MaskType maskType, bool needsTransform, int glyphCount, SkRect deviceRect, SkColor luminanceColor,
        bool useGammaCorrectDistanceTable, uint32_t DFGPFlags, Geometry *geo, GrPaint &&paint);

    GrProgramInfo *programInfo() override
    {
        // TODO [PI]: implement
        return nullptr;
    }

    void addGeometry(Geometry *geometry)
    {
        *fTail = geometry;
        // The geometry may have many entries. Find the end.
        do {
            fTail = &(*fTail)->fNext;
        } while (*fTail != nullptr);
    }

    void onCreateProgramInfo(const GrCaps *, SkArenaAlloc *, const GrSurfaceProxyView &writeView, bool usesMSAASurface,
        GrAppliedClip &&, const GrDstProxyView &, GrXferBarrierFlags renderPassXferBarriers,
        GrLoadOp colorLoadOp) override
    {
        // We cannot surface the AtlasTextOp's programInfo at record time. As currently
        // implemented, the GP is modified at flush time based on the number of pages in the
        // atlas.
    }

    void onPrePrepareDraws(GrRecordingContext *, const GrSurfaceProxyView &writeView, GrAppliedClip *,
        const GrDstProxyView &, GrXferBarrierFlags renderPassXferBarriers, GrLoadOp colorLoadOp) override
    {
        // TODO [PI]: implement
    }

    void onPrepareDraws(GrMeshDrawTarget *) override;
    void onExecute(GrOpFlushState *, const SkRect &chainBounds) override;

#if defined(GR_TEST_UTILS)
    SkString onDumpInfo() const override;
#endif

    skgpu::MaskFormat maskFormat() const
    {
        switch (this->maskType()) {
            case MaskType::kLCDCoverage:
                return skgpu::MaskFormat::kA565;
            case MaskType::kColorBitmap:
                return skgpu::MaskFormat::kARGB;
            case MaskType::kGrayscaleCoverage:
#if !defined(SK_DISABLE_SDF_TEXT)
            case MaskType::kAliasedDistanceField:
            case MaskType::kGrayscaleDistanceField:
            case MaskType::kLCDDistanceField:
            case MaskType::kLCDBGRDistanceField:
#endif
                return skgpu::MaskFormat::kA8;
        }
        // SkUNREACHABLE;
        return skgpu::MaskFormat::kA8;
    }

#if !defined(SK_DISABLE_SDF_TEXT)
    bool usesDistanceFields() const
    {
        return MaskType::kAliasedDistanceField == this->maskType() ||
            MaskType::kGrayscaleDistanceField == this->maskType() || MaskType::kLCDDistanceField == this->maskType() ||
            MaskType::kLCDBGRDistanceField == this->maskType();
    }

    bool isLCD() const
    {
        return MaskType::kLCDCoverage == this->maskType() || MaskType::kLCDDistanceField == this->maskType() ||
            MaskType::kLCDBGRDistanceField == this->maskType();
    }
#else
    bool isLCD() const
    {
        return MaskType::kLCDCoverage == this->maskType();
    }
#endif

    inline void createDrawForGeneratedGlyphs(GrMeshDrawTarget *target, FlushInfo *flushInfo) const;

    MaskType maskType() const
    {
        return static_cast<MaskType>(fMaskType);
    }

    CombineResult onCombineIfPossible(GrOp *t, SkArenaAlloc *, const GrCaps &caps) override;

#if !defined(SK_DISABLE_SDF_TEXT)
    GrGeometryProcessor *setupDfProcessor(SkArenaAlloc *, const GrShaderCaps &, const SkMatrix &localMatrix,
        const GrSurfaceProxyView *views, unsigned int numActiveViews) const;
#endif

    GrProcessorSet fProcessors;
    int fNumGlyphs; // Sum of glyphs in each geometry's subrun

    // All combinable atlas ops have equal bit field values
    uint32_t fDFGPFlags : 10;      // Distance field properties
    uint32_t fMaskType : 3;        // MaskType
    uint32_t fUsesLocalCoords : 1; // Filled in post processor analysis
    uint32_t fNeedsGlyphTransform : 1;
    uint32_t fHasPerspective : 1; // True if perspective affects draw
    uint32_t fUseGammaCorrectDistanceTable : 1;
    static_assert(kMaskTypeCount <= 8, "MaskType does not fit in 3 bits");
#if !defined(SK_DISABLE_SDF_TEXT)
    static_assert(kInvalid_DistanceFieldEffectFlag <= (1 << 9), "DFGP Flags do not fit in 10 bits");
#endif

    // Only needed for color emoji
    sk_sp<GrColorSpaceXform> fColorSpaceXform;

    // Only used for distance fields; per-channel luminance for LCD, or gamma-corrected luminance
    // for single-channel distance fields.
    const SkColor fLuminanceColor{ 0 };

    Geometry *fHead{ nullptr };
    Geometry **fTail{ &fHead };

    using INHERITED = GrMeshDrawOp;
};
} // namespace skgpu::ganesh

#endif
// skgpu_ganesh_AtlasTextOp_DEFINED
